﻿using System;
using COFE.Core;
using COFE.Core.Profile;
using NUnit.Framework;

namespace COFE.Data.Lister
{
    public class DBPathParser : IPathParser
    {
        #region Constructor

        public DBPathParser(IPathParser referencePathParser = null)
        {
            _referencePathParser = referencePathParser != null ? referencePathParser :
                new BasicPathParser();
        }

        #endregion

        #region Methods

        private bool expired(DateTime refreshTIme)
        {
            return refreshTIme < DateTime.UtcNow.Subtract(COFEDBShared.TTLEntryExist);
        }

        private bool lookupPath(IUserProfile profile, string fullParsePath, out Entry dbEntry, out IFileSystemInfo nonCachableEntry)
        {
            string parsePath, listerKey;

            COFEPath.ParseFullParseName(fullParsePath, out listerKey, out parsePath);

            nonCachableEntry = null;

            dbEntry = COFEDBShared.DBOperations.ReadDatabase((context) =>
                COFEDBShared.DBEntryManager.LookupEntry(context, profile, fullParsePath));

            if (dbEntry == null || expired(dbEntry.RefreshTime))
            {
                var refEntry =
                _referencePathParser.TryParsePath(profile, fullParsePath, ParsableType.ParsePath,
                EntryConstructionFlags.DefaultWithoutCache | EntryConstructionFlags.LookupIfParseFailed) as IInternalFileSystemInfoExA;

                if (refEntry != null)
                {
                    COFEAttributes cofeAttribute = (COFEAttributes)refEntry.GetProperty(FileSystemInfoProperties.COFEAttributes).Value;
                    bool nonCachable = cofeAttribute.HasFlag(COFEAttributes.NotCachable);
                    bool alreadyCached = cofeAttribute.HasFlag(COFEAttributes.AlreadyCached);

                    if (nonCachable || alreadyCached)
                    {
                        nonCachableEntry = refEntry;
                        if (alreadyCached)
                            return true;
                    }
                }

                if (refEntry != null && refEntry.IsPropertySupported(DirectoryInfoProperties.FileBasedFSFile))
                    refEntry = refEntry.GetProperty(DirectoryInfoProperties.FileBasedFSFile).Value as IInternalFileInfoExA;

                if (refEntry != null) //found, may or may not in database.
                {
                    dbEntry = COFEDBShared.DBEntryManager.UpdateEntry(refEntry.PrimaryPropertyProvider);
                }
                else if (dbEntry != null) //not found (refEntry = null) and in database.
                {
                    COFEDBShared.DBEntryManager.RemoveEntry(fullParsePath);
                    dbEntry = null;
                }

            }

            return dbEntry != null;
        }

        private bool lookupGuid(IUserProfile profile, Guid entryGuid, out Entry dbEntry)
        {
            dbEntry = COFEDBShared.DBOperations.ReadDatabase((context) =>
                {
                    return COFEDBShared.DBEntryManager.LookupEntry(context, profile, entryGuid);
                });
            return dbEntry != null;
        }

        public IFileSystemInfo TryParsePath(IUserProfile profile, string path, ParsableType type,
            EntryConstructionFlags flags = EntryConstructionFlags.Default)
        {
            Entry dbEntry;

            if (!flags.HasFlag(EntryConstructionFlags.EnableCache))
            {
                if (type == ParsableType.Guid &&
                    !_referencePathParser.SupportedParseType.HasFlag(type))
                {                                        
                    if (lookupGuid(profile, new Guid(path), out dbEntry))
                    {
                        return _referencePathParser.TryParsePath(
                            profile, dbEntry.ParsePath, ParsableType.ParsePath, flags);
                    }
                    else return null;

                }
                else
                    return _referencePathParser.TryParsePath(profile, path, type, flags);
            }

            Func<Entry, IFileSystemInfo> createEntry = (e) =>
                {
                    var cofeAttribute = (COFEAttributes)e.COFEAttributes;
                    if (cofeAttribute.HasFlag(COFEAttributes.NotCachable))
                        return _referencePathParser.TryParsePath(profile, e.ParsePath, ParsableType.ParsePath, flags);
                    else
                        if (cofeAttribute.HasFlag(COFEAttributes.AlreadyCached))
                            return _referencePathParser.TryParsePath(profile, path, type, flags);
                        else
                            return COFEShared.EntryConstructor.CreateEntry(e.SecondaryVolumeId,
                                new DBCachePropertyProvider(e.EntryId), flags);
                };


            IFileSystemInfo nonCachableEntry = null;
            if (type == ParsableType.ParsePath || type == ParsableType.ParsePathLookup)
                if (lookupPath(profile, path, out dbEntry, out nonCachableEntry))
                {
                    if (nonCachableEntry != null)
                        return nonCachableEntry;
                    else
                        return createEntry(dbEntry);
                }

            if (type == ParsableType.Guid)
                if (lookupGuid(profile, Guid.Parse(path), out dbEntry))
                    return createEntry(dbEntry);

            return null;
        }

        public bool Exists(IUserProfile profile, string path, ParsableType type, out bool isFolder)
        {
            Assert.IsNotNull(profile);
            Entry foundEntry = COFEDBShared.DBOperations.ReadDatabase((context) =>
                {
                    switch (type)
                    {
                        case ParsableType.ParsePath:
                        case ParsableType.ParsePathLookup:
                            return COFEDBShared.DBEntryManager.LookupEntry(context, profile, path);
                        case ParsableType.Guid:
                            return COFEDBShared.DBEntryManager.LookupEntry(context, profile, new Guid(path));
                        default: throw new NotSupportedException();
                    }
                });

            isFolder = true;
            if (foundEntry == null)
                return TryParsePath(profile, path, type, EntryConstructionFlags.Default) != null;
            else
            {
                isFolder = foundEntry is FolderEntry;
                return true;
            }
        }

        #endregion

        #region Data

        private IPathParser _referencePathParser;

        #endregion

        #region Public Properties

        public ParsableType SupportedParseType { get { return ParsableType.Guid | ParsableType.ParsePath | ParsableType.ParsePathLookup; } }

        #endregion

    }
}
